﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Web;
using log4net;

namespace MyLib.Web {
	/// <summary>
	/// 
	/// </summary>
	public abstract class MyRestfulService : System.Web.IHttpAsyncHandler, IDbContext {
		private ILog m_logger;

		private HttpHandlerResourceManager<MyRestfulService> m_httpResource;

		private Newtonsoft.Json.JsonSerializer m_jsonSerializer = null;

		private HttpContext m_context;

		/// <summary>
		/// 
		/// </summary>
		protected virtual HttpContext Context {
			get {
				return m_context == null ? HttpContext.Current : m_context;
			}
		}

		/// <summary>
		/// 
		/// </summary>
		public MyRestfulService() {
			m_logger = LogManager.GetLogger(GetType());

			var jsonSerializerSettings = new Newtonsoft.Json.JsonSerializerSettings();
			jsonSerializerSettings.DateFormatString = "s"; // ISO 8601
			m_jsonSerializer = Newtonsoft.Json.JsonSerializer.Create(jsonSerializerSettings);
		}

		#region Event

		/// <summary>
		/// 
		/// </summary>
		public event EventHandler PreLoadResources;

		/// <summary>
		/// 
		/// </summary>
		protected virtual void OnPreLoadResources() {
			if (PreLoadResources != null) {
				PreLoadResources(this, EventArgs.Empty);
			}
		}

		#endregion

		#region IHttpAsyncHandler

		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		/// <param name="cb"></param>
		/// <param name="extraData"></param>
		/// <returns></returns>
		public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData) {
			Action func = () => {
				HttpContext.Current = context;

				ProcessRequest(context);
			};

			return func.BeginInvoke(cb, extraData);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="result"></param>
		public void EndProcessRequest(IAsyncResult result) {
		}

		/// <summary>
		/// 
		/// </summary>
		public bool IsReusable {
			get {
				return false;
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		public void ProcessRequest(System.Web.HttpContext context) {
			System.Diagnostics.Debug.Assert(context != null);

			m_context = context;

			m_httpResource = new HttpHandlerResourceManager<MyRestfulService>(this, Context);

			try {
				// Check authority.
				CheckAuthoriy(context, EssentialActions);

				OnPreLoadResources();

				var httpMethod = context.Request.HttpMethod.ToUpper();
				object ret = null;

				string action = context.Request.QueryString["action"];
				if (action == null) {
					action = "";
				} else {
					action = action.Trim();
				}

				if (httpMethod == "GET") {
					ret = ExecuteGet(action, context.Request.QueryString);
				} else if (httpMethod == "POST") {
					ret = ExecutePost(action, context.Request.Form);
				} else if (httpMethod == "DELETE") {
					ret = ExecuteDelete(action, context.Request.QueryString);
				} else if (httpMethod == "PUT") {
					ret = ExecutePut(action, context.Request.QueryString);
				} else {
					ThrowMethodNotAllowedException(httpMethod);
				}

				var jsonBuffer = new System.Text.StringBuilder();
				using (var tw = new System.IO.StringWriter(jsonBuffer)) {
					m_jsonSerializer.Serialize(tw, ret);
				}

				context.Response.Write(jsonBuffer.ToString());
			} finally {
				m_httpResource.Close();
			}
		}

		#endregion IHttpAsyncHandler

		/// <summary>
		/// 
		/// </summary>
		protected HttpHandlerResourceManager<MyRestfulService> HttpResource {
			get {
				return m_httpResource;
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="action"></param>
		/// <param name="request"></param>
		/// <returns></returns>
		protected virtual object ExecuteGet(string action, NameValueCollection request) {
			ThrowMethodNotAllowedException("GET");

			return null;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="action"></param>
		/// <param name="request"></param>
		/// <returns></returns>
		protected virtual object ExecutePut(string action, NameValueCollection request) {
			ThrowMethodNotAllowedException("PUT");

			return null;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="action"></param>
		/// <param name="request"></param>
		/// <returns></returns>
		protected virtual object ExecuteDelete(string action, NameValueCollection request) {
			ThrowMethodNotAllowedException("DELETE");

			return null;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="action"></param>
		/// <param name="request"></param>
		/// <returns></returns>
		protected virtual object ExecutePost(string action, NameValueCollection request) {
			ThrowMethodNotAllowedException("POST");

			return null;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="httpMethod"></param>
		private static void ThrowMethodNotAllowedException(string httpMethod) {
			throw new HttpException(405, "Method not allowed: \"" + httpMethod + "\".");
		}

		#region IDbContext

		SessionFactory IDbContext.SessionFactory {
			get {
				return m_httpResource.GetSessionFactory();
			}
		}

		#endregion

		/// <summary>
		/// 
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <returns></returns>
		protected internal T GetDbProvider<T>() where T : DbContextProvider, new() {
			return (T)m_httpResource.GetDbProvider(typeof(T));
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="collection"></param>
		/// <param name="key"></param>
		/// <returns></returns>
		protected string GetString(NameValueCollection collection, string key) {
			var ret = collection[key];
			if (ret == null) {
				return "";
			} else {
				return ret;
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="collection"></param>
		/// <param name="key"></param>
		/// <returns></returns>
		protected int? GetInteger(NameValueCollection collection, string key) {
			var ret = collection[key];
			if (ret == null) {
				return null;
			} else {
				return int.Parse(ret.Trim());
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="collection"></param>
		/// <param name="key"></param>
		/// <returns></returns>
		protected long? GetLong(NameValueCollection collection, string key) {
			var ret = collection[key];
			if (ret == null) {
				return null;
			} else {
				return long.Parse(ret.Trim());
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="collection"></param>
		/// <param name="key"></param>
		/// <returns></returns>
		protected decimal? GetDecimal(NameValueCollection collection, string key) {
			var ret = collection[key];
			if (ret == null) {
				return null;
			} else {
				return decimal.Parse(ret.Trim());
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="collection"></param>
		/// <param name="key"></param>
		/// <returns></returns>
		protected double? GetDouble(NameValueCollection collection, string key) {
			var ret = collection[key];
			if (ret == null) {
				return null;
			} else {
				return double.Parse(ret.Trim());
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="collection"></param>
		/// <param name="key"></param>
		/// <returns></returns>
		protected DateTime? GetDateTime(NameValueCollection collection, string key) {
			var ret = collection[key];
			if (ret == null) {
				return null;
			} else {
				return DateTime.ParseExact(ret.Trim(), "s", null); // ISO 8601
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="collection"></param>
		/// <param name="key"></param>
		/// <returns></returns>
		protected bool? GetBoolean(NameValueCollection collection, string key) {
			var ret = collection[key];
			if (ret == null) {
				return null;
			} else {
				ret = ret.Trim().ToLower();
				return ret == "true" || ret == "yes" || ret == "1" || ret == "on";
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="collection"></param>
		/// <param name="key"></param>
		/// <returns></returns>
		protected byte[] GetBinary(NameValueCollection collection, string key) {
			var ret = collection[key];
			if (ret == null) {
				return new byte[0];
			} else {
				return Convert.FromBase64String(ret.Trim());
			}
		}

		/// <summary>
		/// 
		/// </summary>
		protected virtual int[] EssentialActions {
			get {
				return new int[0];
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		/// <param name="actionIdArray"></param>
		/// <returns></returns>
		protected bool IsAuthorized(System.Web.HttpContext context, params int[] actionIdArray) {
			if (actionIdArray == null || actionIdArray.Length == 0) {
				return true;
			}

			if (context.User == null || context.User.Identity == null || string.IsNullOrEmpty(context.User.Identity.Name)) {
				return false;
			} else if (!GetDbProvider<MyLib.Data.AuthorityProvider>().IsAuthorized(1, context.User.Identity.Name, actionIdArray)) {
				return false;
			} else {
				return true;
			}
		}

#pragma warning disable 0618
		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		/// <param name="actionIdArray"></param>
		protected void CheckAuthoriy(System.Web.HttpContext context, params int[] actionIdArray) {
			if (!IsAuthorized(context, actionIdArray)) {
				throw new AccessDeniedHttpException();
			}
		}
#pragma warning restore 0618
	} // end of MyRestfulService.
}
